/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package sq;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import javax.xml.stream.XMLStreamException;

import menu.Menu;
import menu.MenuButton.ButtonType;

import org.lwjgl.LWJGLException;
import org.newdawn.slick.Animation;
import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import sq.game.Quest;
import units.Unit;

/** Main class for the Role-Playing Game engine.
 * Handles initialisation, input and rendering.
 */
public class RPG extends BasicGame {

    /** Screen width, in pixels. */
    private static int screenwidth = 800;
    /** Screen height, in pixels. */
    private static int screenheight = 600;

    public enum State {

        LOADING, MENU, EPILOGUE, GAMEOVER, PROLOGUE, GAME
    }
    private static Image loading;
    private static SoundSettings snds;
    private boolean introStarted, wonStarted;
    private String loadMsg;
    private State state, prevState, nxtState;
    private Menu menu;
    private Quest currentQuest;
    private ArrayList<Quest> quests;

    /** Create a new RPG object. */
    public RPG() {
        super("Shadow Quest v0.9");
//        RPG.setScreenWidth(1024);
//        RPG.setScreenHeight(768);
    }

    /** Initialise the game state.
     * @param gc The Slick game container object.
     */
    @Override
    public void init(GameContainer gc)
            throws SlickException {

        this.quests = new ArrayList<Quest>();
        snds = new SoundSettings();
        gc.getGraphics().clear();
        setLoadingImage(new Image("assets/res/loading.png"));
        FontLoader.initialiseFonts();
        ImageLoader.initialise();
        this.showLoading(State.LOADING, State.MENU, "Initialising Resources...");

    }

    /** Update the game state for a frame.
     * @param gc The Slick game container object.
     * @param delta Time passed since last frame (milliseconds).
     */
    @Override
    public void update(GameContainer gc, int delta)
            throws SlickException {

        if (this.getState() == State.GAME) {

            this.updateStateGame(gc, delta);

        } else if (this.getState() == State.EPILOGUE) {

            this.updateStateEpilogue(gc, delta);

        } else if (this.getState() == State.PROLOGUE) {

            this.updateStatePrologue(gc, delta);

        } else if (this.getState() == State.MENU) {

            this.updateStateMenu(gc, delta);

        } else if (this.getState() == State.LOADING) {

            this.updateStateLoading(gc, delta);

        }

    }

    /** Update when the state of the game is GAME.
     * @param gc The game container.
     * @param delta The time passed since last frame (milliseconds).
     * @throws SlickException
     */
    private void updateStateGame(GameContainer gc, int delta)
            throws SlickException {

        if (this.getCurrentQuest().isGameStarted() && this.getCurrentQuest().isGameEnded()) {
            this.setState(State.PROLOGUE);
            return;
        }

        Input input = gc.getInput();

        boolean menu_key = input.isKeyPressed(Input.KEY_ESCAPE);

        if (menu_key) {
            this.setState(State.MENU);
        }

        // make sure the state has not changed
        if (this.getState() == State.GAME) {
            this.getCurrentQuest().update(input, delta);
        }

    }

    /** Update when the state of the game is GAME.
     * @param gc The game container.
     * @param delta The time passed since last frame (milliseconds).
     * @throws SlickException
     */
    private void updateStateEpilogue(GameContainer gc, int delta)
            throws SlickException {

        // Get data about the current input (keyboard state).
        Input input = gc.getInput();

        boolean skip_key = input.isKeyPressed(Input.KEY_ESCAPE);
        boolean next_key = input.isKeyPressed(Input.KEY_SPACE);

        if (!this.isEpilogueStarted()) {
            this.setIntroStarted(true);
            this.getCurrentQuest().getEpilogue().restart();
        }

        if (skip_key || (this.getCurrentQuest().getEpilogue().isStopped() && this.isEpilogueStarted()))
        {
            this.getCurrentQuest().startGame();
            this.setState(State.GAME);
            return;
        }

        if (next_key && !this.getCurrentQuest().getEpilogue().isStopped() && this.isEpilogueStarted())
        {
            int i = this.getCurrentQuest().getEpilogue().getFrame() + 1;
            if (i < this.getCurrentQuest().getEpilogue().getFrameCount())
            {
                this.getCurrentQuest().getEpilogue().setCurrentFrame(i);
            }
            else
            {
                this.getCurrentQuest().getEpilogue().stop();
            }
        }

    }

    /** Update when the state of the game is GAME.
     * @param gc The game container.
     * @param delta The time passed since last frame (milliseconds).
     * @throws SlickException
     */
    private void updateStatePrologue(GameContainer gc, int delta)
            throws SlickException {

        // Get data about the current input (keyboard state).
        Input input = gc.getInput();

        boolean skip_key = input.isKeyPressed(Input.KEY_ESCAPE);
        boolean next_key = input.isKeyPressed(Input.KEY_SPACE);

        if (!this.isPrologueStarted()) {
            this.setWonStarted(true);
            this.getCurrentQuest().getPrologue().restart();
        }

        if (skip_key || (this.getCurrentQuest().getPrologue().isStopped() && this.isPrologueStarted()))
        {
            this.getCurrentQuest().resetGame();
            this.setState(State.MENU);
            return;
        }

        if (next_key && !this.getCurrentQuest().getPrologue().isStopped() && this.isPrologueStarted())
        {
            int i = this.getCurrentQuest().getPrologue().getFrame() + 1;
            if (i < this.getCurrentQuest().getPrologue().getFrameCount())
            {
                this.getCurrentQuest().getPrologue().setCurrentFrame(i);
            }
            else
            {
                this.getCurrentQuest().getPrologue().stop();
            }
        }

    }

    /** Update when the state of the game is MENU.
     * @param gc The game container.
     * @param delta The time passed since last frame (milliseconds).
     * @throws SlickException
     */
    private void updateStateMenu(GameContainer gc, int delta)
            throws SlickException {

        Quest q = this.getCurrentQuest();
        // Get data about the current input (mouse state).
        Input input = gc.getInput();
        ImageLoader.getMenuCursor().update(delta);

        // Get the user's mouse interactions.
        boolean mouse_down = input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON);
        boolean resume_key = input.isKeyPressed(Input.KEY_ESCAPE);

        if (this.getMenu().getMenuState() == ButtonType.NONE) {
            if (q != null && q.isGameStarted() && !this.getMenu().isError()
                    && !this.getMenu().isBusy() && resume_key) {
                this.setState(State.GAME);
                return;
            }
        }

        // make sure the state has not changed
        if (this.getState() == State.MENU) {
            menu.update(input, delta);
        }

        if (mouse_down) {

            if (this.getMenu().getMenuState() == ButtonType.EXIT) {

                gc.exit();

            } else if (this.getMenu().getMenuState() == ButtonType.START) {

                // TODO
                this.setCurrentQuest(this.getMenu().getSelectedQuest());
                this.getCurrentQuest().resetGame();
                this.getMenu().setMenuState(ButtonType.NONE);
                this.showLoading(this.getState(), State.EPILOGUE, "Initialising Game Data...");

            } else if (this.getMenu().getMenuState() == ButtonType.SAVE_GAME) {

                this.getMenu().setMenuState(ButtonType.NONE);
                if (!this.getMenu().isError()) {
                    String err = "Error saving game!";
                    if (this.getCurrentQuest() != null && this.getCurrentQuest().isGameStarted()) {
                        try {
                            FileIO.saveGame(this.getCurrentQuest().getWorld(), "data/save.xml");
                        } catch (XMLStreamException e) {
                            this.getMenu().gameError(err);
                        } catch (IOException e) {
                            this.getMenu().gameError(err);
                        }
                        this.getMenu().gameBusy("Saving current game...");
                    } else {
                        err = "Game has not started yet to be saved!";
                        this.getMenu().gameError(err);
                    }
                }

            } else if (this.getMenu().getMenuState() == ButtonType.LOAD_GAME) {

                this.getMenu().setMenuState(ButtonType.NONE);
                this.showLoading(this.getState(), State.GAME, "Loading Game...");

            }

        }

    }

    /** Update when the state of the game is PAUSED.
     * @param gc The game container.
     * @param delta The time passed since last frame (milliseconds).
     * @throws SlickException
     * @throws LWJGLException 
     * @throws IOException 
     * @throws XMLStreamException 
     */
    private void updateStateLoading(GameContainer gc, int delta)
            throws SlickException {

        if (this.getNextState() == State.MENU)
        {

            Unit.init();
            int cursorDelays[] = new int[8];
            Arrays.fill(cursorDelays, 100);
            gc.setMouseCursor(new Image("assets/res/blank.png"), 0, 0);
//        	gc.setAnimatedMouseCursor("assets/res/cursor.tga", 22, 35, 70, 70, cursorDelays);
            FontLoader.initialiseCommonResources();
            ImageLoader.loadMenuImages();
            SoundLoader.loadMenuSounds();
            this.menu = new Menu(this);

           this.setState(this.getNextState());

        } 
        else if (this.getNextState() == State.EPILOGUE)
        {

            if (!ImageLoader.isGameImagesLoaded())
            {
                ImageLoader.loadGameImages();
            }
            if (!FontLoader.isGameDataLoaded())
            {
                FontLoader.initialiseGameData();
            }
            if (!this.getCurrentQuest().isDisplayImagesLoaded())
            {
                this.getCurrentQuest().loadDisplayImages();
            }

            this.getCurrentQuest().initialiseQuest();
            //this.getQuest().getWorld().resetWorld(this, "data/new.xml", "Error loading game data!");

            if (!SoundLoader.isSoundsLoaded())
            {
                SoundLoader.loadGameSounds();
            }

            if (!this.getMenu().isError())
            {
                this.setIntroStarted(false);
                this.setState(this.getNextState());
            } else {
                this.setState(this.getPrevState());
            }

        } else if (this.getNextState() == State.GAME)
        {

            if (!FontLoader.isGameDataLoaded())
            {
                FontLoader.initialiseGameData();
            }
            if (!this.getCurrentQuest().isDisplayImagesLoaded())
            {
                this.getCurrentQuest().loadDisplayImages();
            }
            
            this.getCurrentQuest().getWorld().resetWorld(this, "data/save.xml", "Error loading saved game!");

            if (!SoundLoader.isSoundsLoaded()) {
                SoundLoader.loadGameSounds();
            }

            if (!this.getMenu().isError()) {
                this.getCurrentQuest().startGame();
                this.setState(this.getNextState());
            } else {
                this.setState(this.getPrevState());
            }

        }

    }

    /** Render the entire screen, so it reflects the current game state.
     * @param gc The Slick game container object.
     * @param g The Slick graphics object, used for drawing.
     */
    public void render(GameContainer gc, Graphics g)
            throws SlickException {
        Input input = gc.getInput();
        g.setFont(FontLoader.getFontOriginal());

        if (this.getState() == State.GAME) {

            this.getCurrentQuest().render(gc, g);

        } else if (this.getState() == State.EPILOGUE) {

            this.getCurrentQuest().getEpilogue().draw();
            g.setFont(FontLoader.getFontSize11());
            String str = "Press SPACE to goto the next slide or ESCAPE to skip the Epilogue.";
            int offset = 10, width = g.getFont().getWidth(str), height = g.getFont().getHeight(str);
            float x = (RPG.getScreenWidth() - width) / 2, y = RPG.getScreenHeight() - height - 20;
            g.setColor(FontLoader.getDisplayBgColor());
            g.fillRect(x - offset, y - offset, width + 2f * offset, height + 2f * offset);
            g.setColor(Color.white);
            g.drawString(str, x, y);

        } else if (this.getState() == State.PROLOGUE) {

            this.getCurrentQuest().getPrologue().draw();
            g.setFont(FontLoader.getFontSize11());
            String str = "Press SPACE to goto the next slide or press ESCAPE to skip to the Menu.";
            int offset = 10, width = g.getFont().getWidth(str), height = g.getFont().getHeight(str);
            float x = (RPG.getScreenWidth() - width) / 2, y = RPG.getScreenHeight() - height - 20;
            g.setColor(FontLoader.getDisplayBgColor());
            g.fillRect(x - offset, y - offset, width + 2f * offset, height + 2f * offset);
            g.setColor(Color.white);
            g.drawString(str, x, y);

        } else if (this.getState() == State.MENU) {

            this.menu.render(g);
            Animation cur = ImageLoader.getMenuCursor();
            g.drawAnimation(cur, input.getMouseX() - 22, input.getMouseY() - 36);

        } else if (this.getState() == State.LOADING) {

            this.drawLoadingScreen(g);

        }

    }

    /** Start-up method. Creates the game and runs it.
     * @param args Command-line arguments (ignored).
     */
    public static void main(String[] args)
            throws SlickException {
        AppGameContainer app = new AppGameContainer(new RPG());
        // setShowFPS(true), to show frames-per-second.
        app.setShowFPS(false);
        app.setDisplayMode(RPG.getScreenWidth(), RPG.getScreenHeight(), false);
        app.setSoundOn(true);
        app.setSoundVolume(1f);
        app.start();

    }

    /** Draw the paused image when the game is paused.
     */
    public Menu getMenu() {
        return this.menu;
    }

    /** Draw the paused image when the game is paused.
     */
    public Quest getCurrentQuest() {
        return this.currentQuest;
    }

    /** Draw the paused image when the game is paused.
     */
    public void setCurrentQuest(Quest quest) {
        this.currentQuest = quest;
    }

    /** Show the loading screen.
     * @return The state.
     */
    private void showLoading(State prev, State next, String msg) {
        this.setLoadMessage(msg);
        this.setState(State.LOADING);
        this.setPrevState(prev);
        this.setNextState(next);
    }

    /** Draw the paused image when the game is paused.
     */
    private void drawLoadingScreen(Graphics g) {

        int x = (RPG.getScreenWidth() - getLoadingImage().getWidth()) / 2;
        int y = (RPG.getScreenHeight() - getLoadingImage().getHeight()) / 2;
        getLoadingImage().draw(x, y);
        int text_width = g.getFont().getWidth(this.getLoadMessage());
        x = (RPG.getScreenWidth() - text_width) / 2;
        y += 1.5f * getLoadingImage().getHeight();
        g.setColor(Color.white);
        g.drawString(this.getLoadMessage(), x, y);

    }

    /** Get the state of the game.
     * @return The state.
     */
    private State getState() {
        return this.state;
    }

    /** Set the state of the game.
     * @param state The state.
     */
    private void setState(State state) {

        this.state = state;

        if (state == State.EPILOGUE) {
            this.getCurrentQuest().getEpilogue().restart();
            this.setIntroStarted(true);
        } else if (state == State.MENU) {
            this.getMenu().playMusic();
        } else if (state == State.GAME) {
            this.getCurrentQuest().getWorld().playMusic();
        }

        // stop playing musics
        if (this.getPrevState() == State.MENU) {
            this.getMenu().stopMusic();
        } else if (this.getPrevState() == State.GAME) {
            this.getCurrentQuest().getWorld().stopMusic();
        }

    }

    /** Get the previous state of the game.
     * @return The state.
     */
    private State getPrevState() {
        return this.prevState;
    }

    /** Set the previous state of the game.
     * @param state The state.
     */
    private void setPrevState(State prevState) {
        this.prevState = prevState;
    }

    /** Get the next state of the game.
     * @return The state.
     */
    private State getNextState() {
        return this.nxtState;
    }

    /** Set the next state of the game.
     * @param state The state.
     */
    private void setNextState(State nextState) {
        this.nxtState = nextState;
    }

    public static Image getLoadingImage() {
        return loading;
    }

    public static void setLoadingImage(Image loading) {
        RPG.loading = loading;
    }

    private String getLoadMessage() {
        return this.loadMsg;
    }

    private void setLoadMessage(String loadMsg) {
        this.loadMsg = loadMsg;
    }

    private boolean isEpilogueStarted() {
        return this.introStarted;
    }

    private void setIntroStarted(boolean introStarted) {
        this.introStarted = introStarted;
    }

    private boolean isPrologueStarted() {
        return this.wonStarted;
    }

    private void setWonStarted(boolean wonStarted) {
        this.wonStarted = wonStarted;
    }

    public static SoundSettings getSoundSettings() {
        return snds;
    }

    /** Get the screen width associated with the world.
     * @return The screen width.
     */
    public static int getScreenWidth() {
        return RPG.screenwidth;
    }

    /** Set the screen width associated with the world.
     * @param width The screen width.
     */
    public static void setScreenWidth(int width) {
        RPG.screenwidth = width;
    }

    /** Get the screen height associated with the world.
     * @return The screen height.
     */
    public static int getScreenHeight() {
        return RPG.screenheight;
    }

    /** Set the screen height associated with the world.
     * @param height The screen height.
     */
    public static void setScreenHeight(int height) {
        RPG.screenheight = height;
    }

    /**
     * @return the quests
     */
    public ArrayList<Quest> getQuests() {
        return this.quests;
    }

    
}
